/*
 * Copyright 2014 Щекотова Ангелина Александровна.
 * Этот файл — часть J4JClientServer Framework
 * J4JClientServer Framework - свободная программа: вы можете перераспространять ее и/или изменять
 * ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде, в каком
 * она была опубликована Фондом свободного программного обеспечения; либо
 * версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.
 *
 * J4JClientServer Framework распространяется в надежде, что она будет полезной,
 * но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
 * или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
 * общественной лицензии GNU.
 *
 * Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
 * вместе с этой программой. Если это не так, см.
 * <http://www.gnu.org/licenses/>.
 */
package ru.java4java.utils;

import org.apache.log4j.Logger;

import java.io.*;

public final class ByteUtils {

    private static final Logger log = Logger.getLogger(ByteUtils.class);

    public static final int SIZE_OF_BYTE    = 1;
    public static final int SIZE_OF_SHORT   = 2;
    public static final int SIZE_OF_CHAR    = 2;
    public static final int SIZE_OF_INTEGER = 4;
    public static final int SIZE_OF_FLOAT   = 4;
    public static final int SIZE_OF_LONG    = 8;
    public static final int SIZE_OF_DOUBLE  = 8;

    public static boolean createBoolean(byte b) {
        return (b != 0);
    }

    public static byte createByte(byte b) {
        return b;
    }

    /**<pre>
     * Create unsigned Byte value from byte.
     * In other words, it is converted byte to integer value ignoring sign
     * </pre>
     * @param b byte value
     * @return Integer value without sign
     */
    public static int createUnsignedByte(byte b) {
        int result = b << 0;
        return result;
    }

    public static short createShort(int b1, int b2) {
        return (short)(
          (b1 << 8) +
          (b2 << 0)
        );
    }

    /**<pre>
     * Create unsigned short value from two bytes.
     * In other words, it is converted short from two bytes to integer value ignoring sign
     * </pre>
     * @param b1 left byte value
     * @param b2 right byte value
     * @return Integer value without sign
     */
    public static int createUnsignedShort(int b1, int b2) {
        return ((b1 << 8) + (b2 << 0));
    }

    public static char createChar(int b1, int b2) {
        return (char)((b1 << 8) + (b2 << 0));
    }

    public static int createInteger(int ch1, int ch2, int ch3, int ch4) {
        return ((ch1 << 24) + (ch2 << 16) + (ch3 <<  8) + (ch4 <<  0));
    }

    public static long createLong(int ch1, int ch2, int ch3, int ch4, int ch5, int ch6, int ch7, int ch8) {
        return (
          ((long)ch1 << 56) +
          ((long)(ch2 & 255) << 48) +
          ((long)(ch3 & 255) << 40) +
          ((long)(ch4 & 255) << 32) +
          ((long)(ch5 & 255) << 24) +
          ((ch6 & 255) << 16) +
          ((ch7 & 255) <<  8) +
          ((ch8 & 255) <<  0)
        );
    }

    public static float createFloat(int ch1, int ch2, int ch3, int ch4) {
        return Float.intBitsToFloat(((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4)));
    }

    public static double createDouble(int ch1, int ch2, int ch3, int ch4, int ch5, int ch6, int ch7, int ch8) {
        return Double.longBitsToDouble(createLong(ch1, ch2, ch3, ch4, ch5, ch6, ch7, ch8));
    }

    /**<pre>
     * Maybe work if java to java ;)
     * Use String class -> new String(byte bytes[], int offset, int length);
     *
     * Please look:
     * {@link String#String(byte[], int, int)}
     * {@link String#String(byte[], int, int, java.nio.charset.Charset)}
     * {@link String#String(byte[], int, int, String)}
     * </pre>
     * @param offset offset of index data array
     * @param length length of string for read
     * @param data array with string
     * @return String
     */
    @Deprecated
    public static String createString(int offset, int length, byte[] data) {
        return new String(data, offset, length);
    }

    /**<pre>
     * Read UTF String from array data of byte
     * Used {@link DataInputStream#readUTF()} (20.02.2014)
     * </pre>
     * @param offset offset of index data array
     * @param length length of string for read
     * @param data array with string
     * @return UTF String
     */
    public static String createUTF(int offset, int length, byte[] data) {
        InputStream inputStream = new ByteArrayInputStream(data, offset, length);
        DataInputStream dataInputStream = new DataInputStream(inputStream);
        String result = null;
        try {
            result = dataInputStream.readUTF();
        } catch (IOException e) {
            log.error("IOException", e);
        }
        return result;
    }

    public static int sizeOfString(String string) {
        new String(new byte[]{});
        return string.length() * SIZE_OF_CHAR;
    }

    public static boolean getBoolean(byte[] b, int off) {
        return b[off] != 0;
    }

    public static char getChar(byte[] b, int off) {
        return (char) ((b[off + 1] & 0xFF) +
          (b[off] << 8));
    }

    public static short getShort(byte[] b, int off) {
        return (short) ((b[off + 1] & 0xFF) +
          (b[off] << 8));
    }

    public static int getInt(byte[] b, int off) {
        return ((b[off + 3] & 0xFF)      ) +
          ((b[off + 2] & 0xFF) <<  8) +
          ((b[off + 1] & 0xFF) << 16) +
          ((b[off    ]       ) << 24);
    }

    public static float getFloat(byte[] b, int off) {
        return Float.intBitsToFloat(getInt(b, off));
    }

    public static long getLong(byte[] b, int off) {
        return ((b[off + 7] & 0xFFL)      ) +
          ((b[off + 6] & 0xFFL) <<  8) +
          ((b[off + 5] & 0xFFL) << 16) +
          ((b[off + 4] & 0xFFL) << 24) +
          ((b[off + 3] & 0xFFL) << 32) +
          ((b[off + 2] & 0xFFL) << 40) +
          ((b[off + 1] & 0xFFL) << 48) +
          (((long) b[off])      << 56);
    }

    public static double getDouble(byte[] b, int off) {
        return Double.longBitsToDouble(getLong(b, off));
    }

    public static void putBoolean(byte[] b, int off, boolean val) {
        b[off] = (byte) (val ? 1 : 0);
    }

    public static void putChar(byte[] b, int off, char val) {
        b[off + 1] = (byte) (val      );
        b[off    ] = (byte) (val >>> 8);
    }

    public static void putShort(byte[] b, int off, short val) {
        b[off + 1] = (byte) (val      );
        b[off    ] = (byte) (val >>> 8);
    }

    public static void putInt(byte[] b, int off, int val) {
        b[off + 3] = (byte) (val       );
        b[off + 2] = (byte) (val >>>  8);
        b[off + 1] = (byte) (val >>> 16);
        b[off    ] = (byte) (val >>> 24);
    }

    public static void putFloat(byte[] b, int off, float val) {
        putInt(b, off,  Float.floatToIntBits(val));
    }

    public static void putLong(byte[] b, int off, long val) {
        b[off + 7] = (byte) (val       );
        b[off + 6] = (byte) (val >>>  8);
        b[off + 5] = (byte) (val >>> 16);
        b[off + 4] = (byte) (val >>> 24);
        b[off + 3] = (byte) (val >>> 32);
        b[off + 2] = (byte) (val >>> 40);
        b[off + 1] = (byte) (val >>> 48);
        b[off    ] = (byte) (val >>> 56);
    }

    public static void putDouble(byte[] b, int off, double val) {
        putLong(b, off, Double.doubleToLongBits(val));
    }

    public static class IntegerBitOperations {
        /**
         *
         * @param value value of flags
         * @param index bit index into value
         * @param operation 1 - get bit, 2 - write bit, 3 - delete bit, default - inverse bit
         * @return
         */
        public static int bit(int value, final int index, final int operation) {
            switch (operation) {
                case 1: return value & (1 << index);
                case 2: return value | (1 << index);
                case 3: return value & ~(1 << index);
                default: return value ^ (1 << index);
            }
        }
        /* Operation of bits */
        public static int bitRead(final int value, final int index) { return (byte)(value & (1 << index)); }
        public static boolean bitStatus(final int value, final int index) { return (value&(1<<index)) == 1; }
        public int bitWrite(final int value, final int index) { return value | 1 << index; }
        public static int bitDelete(final int value, final int index) { return value & ~(1 << index); }
        public static int bitInverse(final int value, final int index) { return value ^ (1 << index); }
    }

    public static class LongBitOperations {
        /**
         *
         * @param value value of flags
         * @param index bit index into value
         * @param operation 1 - get bit, 2 - write bit, 3 - delete bit, default - inverse bit
         * @return
         */
        public static long bit(long value, final int index, final byte operation) {
            switch (operation) {
                case 1: return value & (1 << index);
                case 2: return value | (1 << index);
                case 3: return value & ~(1 << index);
                default: return value ^ (1 << index);
            }
        }
        /* Operation of bits */
        public static long bitRead(final long value, final byte index) { return (byte)(value & (1 << index)); }
        public static boolean bitStatus(final long value, final byte index) { return (value&(1<<index)) == 1; }
        public long bitWrite(final long value, final byte index) { return value | 1 << index; }
        public static long bitDelete(final long value, final byte index) { return value & ~(1 << index); }
        public static long bitInverse(final long value, final byte index) { return value ^ (1 << index); }
    }


}
